
package org.laobai.meiqia.skill.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.laobai.meiqia.common.constant.KeyConstants;
import org.laobai.meiqia.common.constant.StatusCode;
import org.laobai.meiqia.common.metas.OrderStatus;
import org.laobai.meiqia.common.struct.Pair;
import org.laobai.meiqia.common.util.JsonUtils;
import org.laobai.meiqia.skill.entity.SkillGoods;
import org.laobai.meiqia.skill.entity.SkillOrder;
import org.laobai.meiqia.skill.mapper.SkillGoodsMapper;
import org.laobai.meiqia.skill.service.SkillGoodsService;
import org.laobai.meiqia.skill.struct.PageResult;
import org.laobai.meiqia.skill.util.StringUtils;
import org.laobai.meiqia.skill.vo.SearchVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author jiusan
 * @since 2020-12-13
 */
@Service
public class SkillGoodsServiceImpl extends ServiceImpl<SkillGoodsMapper, SkillGoods> implements SkillGoodsService {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private CacheManager cacheManager;

    private final static Logger LOGGER = LoggerFactory.getLogger(SkillGoodsServiceImpl.class);

    private final static String STOCK_LUA;
    public final static String ROLLBACK_STOCK_LUA;


    static {
        /**
         * -1 库存不受限制，随便买
         * -2 库存不足
         * -3 没有该商品的数据
         * 其余 扣减库存成功
         */
        StringBuilder stockLua = new StringBuilder();
        stockLua.append("if (redis.call('exists', KEYS[1]) == 1) then");
        stockLua.append("    local stock = tonumber(redis.call('get', KEYS[1]));");
        stockLua.append("    local num = tonumber(ARGV[1]);");
        stockLua.append("    if (stock == -1) then");
        stockLua.append("        return -1;");
        stockLua.append("    end;");
        stockLua.append("    if (stock >= num) then");
        stockLua.append("        return redis.call('incrby', KEYS[1], 0 - num);");
        stockLua.append("    end;");
        stockLua.append("    return -2;");
        stockLua.append("end;");
        stockLua.append("return -3;");
        STOCK_LUA = stockLua.toString();

        StringBuilder rollBackStockLua = new StringBuilder();
        rollBackStockLua.append("if (redis.call('exists', KEYS[1]) == 1) then");
        rollBackStockLua.append("    local stock = tonumber(redis.call('get', KEYS[1]));");
        rollBackStockLua.append("    local num = tonumber(ARGV[1]);");
        rollBackStockLua.append("    if (stock >= 0) then");
        rollBackStockLua.append("        return redis.call('incrby', KEYS[1], num);");
        rollBackStockLua.append("    end;");
        rollBackStockLua.append("    return -2;");
        rollBackStockLua.append("end;");
        rollBackStockLua.append("return -3;");
        ROLLBACK_STOCK_LUA = rollBackStockLua.toString();
    }

    @Override
    public PageResult<SkillGoods> page(Integer currPage, Integer pageSize, SearchVO vo) {
        // 分页
        Page<SkillGoods> skillGoodsPage = new Page<>(currPage, pageSize);

        // 条件查询
        QueryWrapper<SkillGoods> wrapper = new QueryWrapper<>();

        if (Objects.nonNull(vo)) {
            String keyWord = vo.getKeyWord();
            if (StringUtils.isNotEmpty(keyWord)) {
                wrapper.like("name", keyWord);
            }
        }
        skillGoodsPage = page(skillGoodsPage, wrapper);
        return PageResult.of(skillGoodsPage.getRecords(), skillGoodsPage.getTotal());
    }

    @Override
    public boolean saveSkillGoods(SkillGoods skillGoods) {
        return save(skillGoods);
    }

    @Override
    public boolean updateSkillGoods(SkillGoods skillGoods) {
        return updateById(skillGoods);
    }

    @Override
    public boolean delById(Long id) {
        return removeById(id);
    }

    @Override
    public boolean delByIds(List<Long> ids) {
        return removeByIds(ids);
    }

    @Override
    public SkillGoods detailById(Long id) {
        String key = KeyConstants.SKILL_GOODS_KEY + id;
        SkillGoods goods = null;
        Cache cache = cacheManager.getCache("skill_goods");
        // 查询以及缓存
        goods = getSkillGoodsFromCache(key, cache);

        if (Objects.nonNull(goods)) {
            return goods;
        }
        // 整个秒杀过程中都不会执行这个代码  == 》代码健康性
        synchronized (this) {
            // 再次访问请求
            goods = getSkillGoodsFromCache(key, cache);
            if (Objects.nonNull(goods)) {
                return goods;
            }
            // 查询数据库
            LOGGER.info("data from DB");
            goods = getById(id);
            if (Objects.isNull(goods)) {
                // 防止缓存击穿
                goods = new SkillGoods();
                goods.setName("数据不存在");
            }
            // 缓存Redis
            redisTemplate.opsForValue().set(key, goods, 1, TimeUnit.DAYS);
            // 缓存到一级缓存
            cache.put(new Element(key, goods));

        }
        return goods;
    }

    @Override
    public Pair<Integer,Object> preReduceStock(Long userId, Long skillId) {
        //查询详情
        SkillGoods skillGoods = detailById(skillId);
        // 未开始
        if(System.currentTimeMillis()<skillGoods.getStartTime().getTime()){
            return Pair.of(StatusCode.Skill.NO_STARTED,"未开始");
        }
        // 已结束
        if(System.currentTimeMillis()>skillGoods.getEndTime().getTime()){
            return Pair.of(StatusCode.Skill.END_ALREADY,"已结束");
        }

        // 判断当前人是否买了
        Boolean member = redisTemplate.opsForSet().isMember(KeyConstants.SKILL_GOODS_USER_ID_RECORD, userId);
        if (member) {
            return Pair.of(StatusCode.Skill.ALREADY_SKILL,"已抢购");
        }

        // 执行lua脚本
        Long num = evalLua(skillId, STOCK_LUA);

        /**-1 库存不受限制，随便买
         * -2 库存不足
         * -3 没有该商品的数据
         * 大于等于0 扣减库存成功*/
        if (num >= 0) {
            // 下订单
            SkillOrder skillOrder = skillOrder(userId, skillId);
            redisTemplate.opsForSet().add(KeyConstants.SKILL_GOODS_USER_ID_RECORD, userId);
            return Pair.of(StatusCode.Skill.SUCCESS,skillOrder);
        }
        return Pair.of(StatusCode.Skill.FAILED,"抢购失败");
    }

    @Override
    public Long evalLua(Long skillId, String rollbackStockLua) {
        return (Long) redisTemplate.execute(
                (RedisConnection connection) -> connection.eval(
                        rollbackStockLua.getBytes(),//lua脚本字符串
                        ReturnType.INTEGER,//设置返回值 byte[]
                        1, //设置key数量
                        (KeyConstants.SKILL_GOODS_STOCK + skillId).getBytes(), //key1
                        "1".getBytes()//Args 值
                )
        );
    }


    public Boolean preReduceStockByRedis(Long userId, Long skillId) {
        // 判断当前人是否买了
        Boolean member = redisTemplate.opsForSet().isMember(KeyConstants.SKILL_GOODS_USER_ID_RECORD, userId);
//        if (member) {
//            return false;
//        }

        Long decrement = redisTemplate.opsForValue().decrement(KeyConstants.SKILL_GOODS_STOCK + skillId);
        if (decrement > 0) {
            // 添加记录
            redisTemplate.opsForSet().add(KeyConstants.SKILL_GOODS_USER_ID_RECORD, userId);
            return true;
        }

        return false;
    }

    public Boolean preReduceStockByDB(Long userId, Long skillId) {
        // 判断当前人是否买了
        Boolean member = redisTemplate.opsForSet().isMember(KeyConstants.SKILL_GOODS_USER_ID_RECORD, userId);
//        if (member) {
//            return false;
//        }

        SkillGoods goods = getById(skillId);

        // 减库存
        goods.setStockCount(goods.getStockCount() - 1);


        LambdaQueryWrapper<SkillGoods> wrapper = new QueryWrapper<SkillGoods>().lambda()
                .eq(SkillGoods::getId, skillId)
                .gt(SkillGoods::getStockCount, 0);

        boolean update = update(goods, wrapper);
        if (update) {
            // 添加记录
            redisTemplate.opsForSet().add(KeyConstants.SKILL_GOODS_USER_ID_RECORD, userId);
        }
        return update;
    }

    public SkillOrder skillOrder(Long userId, Long skillId) {
        // 查询详情
        SkillGoods detail = detailById(skillId);

        // 初始化订单
        SkillOrder skillOrder = new SkillOrder();
        skillOrder.setCreateTime(new Date());
        skillOrder.setStatus(OrderStatus.WAIT_PAY.getCode());
        skillOrder.setPrice(detail.getNewPrice());
        skillOrder.setName(detail.getName());
        skillOrder.setUserId(userId);
        skillOrder.setOrderNo(UUID.randomUUID().toString());
        skillOrder.setSkillId(detail.getId());
        try {
            // 存到Redis
            BoundValueOperations ops = redisTemplate.boundValueOps(KeyConstants.USER_SKILL_ORDER + userId);
            ops.set(skillOrder);

            // 发送到MQ
            rabbitTemplate.convertAndSend("meiqia.skill.order.exchange","", JsonUtils.toString(skillOrder));

        }catch (Exception e){
            e.printStackTrace();
            // 执行lua脚本
            Long num = evalLua(skillId, ROLLBACK_STOCK_LUA);
        }

        return skillOrder;
    }

    private SkillGoods getSkillGoodsFromCache(String key, Cache cache) {
        SkillGoods goods;
        Element element = cache.get(key);
        if (Objects.nonNull(element)) {
            goods = (SkillGoods) element.getObjectValue();
            if (Objects.nonNull(goods)) {
                LOGGER.info("data from ehcache");
                return goods;
            }
        }
        // 从redis找
        goods = (SkillGoods) redisTemplate.opsForValue().get(key);
        if (Objects.nonNull(goods)) {
            // 缓存到一级缓存
            LOGGER.info("data from redis");
            cache.put(new Element(key, goods));
            return goods;
        }
        return null;
    }
}
